﻿
/// <reference path="Container.js" />
/// <reference path="_namespace.js" />


/*****************************************************************************
Ajax library class definition. Kevin Robinson 2006.
Class: ajax.components.Treeview
/*****************************************************************************/

ajax.lang.addNamespace("ajax.components");

//-----------------------------------------------------------------------------
ajax.components.Treeview = function(doc, parentNode, layerName, w, h, callback, method) // Constructor
{
    ajax.components.Treeview.superConstructor.apply(this, arguments);
    this.parentNode     = parentNode;
    this.url            = typeof(callback) == "string" ? callback: "";  
    this.requestParams  = [];// For sending additional info along with the standard key
    this.callback       = typeof(callback) == "function" ? callback : null;   
    this.method         = typeof(method) == "string" ? method : "GET"; // Get by default for speed & caching.
    this.checkboxes     = false;   
    this.selectedNode   = null;
    this.nodeImage      = ajax.getDomainPath() + "/images/folder.png";
    this.nodeImageSelected   = ajax.getDomainPath() + "/images/openfolder.png";
    this.callbackWait   = 200;
            
    // Cache loading image
    this.loadingIconSrc = ajax.getDomainPath() + "/images/ajax-loader7.gif";         
        
    // Styles    
    this.loadingIconStyle = 
    {
        position    : "relative",
        left        : "4px",
        top         : "0px"                                   
    }
    this.divStyle = 
    {
        position: "relative",
        left    : "20px",
        width   : "95%"
    }
    this.imgProperties = 
    {
        align           : "absmiddle",
        width           : "16",
        height          : "16"
    }
    this.txtStyle   =
    {
        color           : "Black",
        backgroundColor : ""   
    }
    this.txtSelectedStyle = 
    {
        color           : "White",
        backgroundColor : "DarkBlue"        
    }
    this.txtMouseOverStyle = 
    {
        textDecoration  : "underline"
    }
    this.txtMouseOutStyle =
    {
        textDecoration  : "none"
    }
    
    // Publish public events
    this.getEvent(this.window, "onNodeExpanded");
    this.getEvent(this.window, "onNodeCollapsed");
    this.getEvent(this.window, "onNodeSelected");
    this.getEvent(this.window, "onNodeUnSelected");
    this.getEvent(this.window, "onItemsLoaded");
    this.getEvent(this.window, "onHttpError");
    this.getEvent(this.window, "onItemsError");
}

//-----------------------------------------------------------------------------
ajax.lang.extend("ajax.components.Treeview", "ajax.components.Container"); // Inherit
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.create = function()
{
    ajax.components.Treeview.superClass.create.call(this);
    // Cache loading image
    var img = new this.window.Image();         
    img.src = this.loadingIconSrc;   
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.addNode = function(node, item)
{ // Passed the parent node and an item, this creates the item under the node.
    var self            = this;
    var div             = this.doc.createElement("div");
    
    this.copyProperties(div.style, this.divStyle);
    
    var icon            = this.doc.createElement("img");
    icon.align          = "absmiddle";
    
    var checkbox        = this.doc.createElement("input");
    checkbox.type       = "checkbox";
    if (!this.checkboxes)
        checkbox.style.display = "none";
    
    var img             = this.doc.createElement("img");    
    this.copyProperties(img, this.imgProperties);
    img.src             = (item.img != "undefined" && item.img != "") ? item.img : this.nodeImage
    var txt             = this.doc.createElement("span");
    txt.innerHTML       = item.words.htmlEncode();

    var txtImg          = this.doc.createElement("span");
    
    // Add some handlers
    if (item.type == "Folder")
    {
        // Add the Plus and the handler to load...
        icon.src = ajax.getDomainPath()+ "/images/plus.gif";
        ajax.lang.getEvent(this.window, icon, "onclick").addListener(function(e)
            {
                self.loadItems(this.parentNode);
            }
        );
    }
    else
        icon.src = ajax.getDomainPath() + "/images/leaf.gif";
        
    ajax.lang.getEvent(this.window, txtImg, "onmouseover").addListener(function(e)
        {
            self.copyProperties(this.parentNode.treeElements.txt.style, self.txtMouseOverStyle); 
        }
    );
    ajax.lang.getEvent(this.window, txtImg, "onmouseout").addListener(function(e)
        {
            self.copyProperties(this.parentNode.treeElements.txt.style, self.txtMouseOutStyle);            
        }
    );
    ajax.lang.getEvent(this.window, txtImg, "onclick").addListener(this.selectNodeFromEvent.bind(this));
    ajax.lang.getEvent(this.window, txtImg, "oncontextmenu").addListener(this.selectNodeFromEvent.bind(this));
    // If not a Lead node then add handler to load more items
    if (item.type != "Leaf")
    {
        ajax.lang.getEvent(this.window, txtImg, "onclick").addListener(function(e, router)
            {
                var node = router.sender.parentNode;
                if (!node.expanded)
                    this.loadItems(node);                
            }.bind(this)
        );
    }

    // Store handles to all elements in the node (formalised)
    // TODO: Needs work: Here we define what a "node" is - a div with extra props and a method...
    div.treeElements = new ajax.components.Treeview.TreeElements(icon, img, checkbox, txt, txtImg);
    div.setText      = function(txt)
    {
        this.treeElements.txt.innerHTML = txt;
        this.dataItem.words             = txt;
    }
    div.dataItem        = item;
    
    //div.treeElements = {icon: icon, img: img, checkbox: checkbox, txt: txt, txtImg: txtImg};
    div.appendChild(icon);
    div.appendChild(this.doc.createTextNode(" "));
    
    txtImg.appendChild(img);
    txtImg.appendChild(this.doc.createTextNode(" "));
    txtImg.appendChild(checkbox);
    txtImg.appendChild(this.doc.createTextNode(" "));
    txtImg.appendChild(txt);    
    div.appendChild(txtImg);
    
    node.appendChild(div);
    return div;
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.removeNodes = function(node)
{
    // Each sub node is actually comprised of three DOM elements (plus/minus icon, checkbox, text)
    for (var i = node.childNodes.length; i >= 3; i--)
        this.removeNode(node.childNodes[i])
               
    this.collapseNode(node);           
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.refreshNode = function(node)
{    
    if (node.dataItem.type == "Leaf")
        return;    
    node.expanded        = false;   // set to be NOT expanded (so loading items does not simply collapse it)
    node.dataItem.loaded = false;   // set back to NOT loaded  
    this.loadItems(node);           // load the node again
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.selectNodeFromEvent = function(e, router)
{
    var node = router.sender.parentNode;
    this.selectNode(node);
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.selectNode = function(node)
{            
    // Unselect any previous node
    this.unSelectNode();
    this.copyProperties(node.treeElements.txt.style, this.txtSelectedStyle);
    node.treeElements.img.src = node.dataItem.imgSel || this.nodeImageSelected;
    this.selectedNode = node;
    var args = {node: node};
    this.onNodeSelected.call(this, args);   
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.unSelectNode = function()
{
    // Clear any current selected node
    if (this.selectedNode)
    {
        this.copyProperties(this.selectedNode.treeElements.txt.style, this.txtStyle);
        this.selectedNode.treeElements.img.src = (this.selectedNode.dataItem.img.length > 0) ? this.selectedNode.dataItem.img : this.nodeImage;
        
        var node = this.selectedNode;        
        this.selectedNode = null;
        this.onNodeUnSelected.call(this, node);
    }    
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.processItems = function(node, items)
{ // Sent the parent node and all loaded items, this creates all items under the node.
    // Hide icon if no content
    if (node.treeElements)
    {
        if (items.length < 1)        
            node.treeElements.icon.style.visibility = "hidden";   
        else
            node.treeElements.icon.style.visibility = "visible";                    
    }
    
    for (var i = 0; i < items.length; i++)
    {
        var item            = items[i];
                
        this.addNode(node, item);
        
        node.expanded = true;
        if (node.treeElements)
            node.treeElements.icon.src = ajax.getDomainPath() + "/images/minus.gif";
    }
    // Loaded so remove loading icon and set node as loaded
    this.removeLoadingIcon(node);
    if (node.dataItem) // Might be base div tag "acting as a node".
        node.dataItem.loaded = true;
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.prototype.loadItems = function(node)
{        
    if (!node)
        node = this.layer;
    // ------------- Deal with the node if it was already loaded.
    if (node.expanded) // simply collapse the node
    {
        this.collapseNode(node);
        return;        
    }  
    //else if (node.treeElements && node.treeElements.txtImg.nextSibling)
    else if (node.treeElements && node.dataItem && node.dataItem.loaded == true) // Simply expand the node
    {
        this.expandNode(node);
        return;
    }     
    
    // Not yet loaded so..
    this.removeNodes(node);         // Remove any Nodes as we are going full a load.
    if (this.url != "")
        this.loadFromUrl(node);
    else if (this.callback)
        this.loadFromCallback(node);
}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.expandNode = function(node)
{
    // Show all children
    for (var i = 0; i < node.childNodes.length; i++)
    {
        var child = node.childNodes[i];
        if (child.tagName && child.tagName.toUpperCase() == "DIV")
            child.style.display = "block";             
    }
    node.childNodes[0].src  = ajax.getDomainPath() + "/images/minus.gif";
    node.expanded           = true;
    var args = {node: node};
    this.onNodeExpanded.call(this, args);    
}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.collapseNode = function(node)
{
    // hide all children
    for (var i = 0; i < node.childNodes.length; i++)
    {
        var child = node.childNodes[i];
        if (child.tagName && child.tagName.toUpperCase() == "DIV")
            child.style.display = "none"; 
    }
    if (node.treeElements)
        node.treeElements.icon.src = ajax.getDomainPath() + "/images/plus.gif";
        
    node.expanded           = false;
    var args = {node: node};
    this.onNodeCollapsed.call(this, args);    
}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.loadFromUrl = function(node)
{
    var self = this;
    var onLoadHandler = function(req)
    {
        var sText     = req.responseText;
        var items     = sText.toJsonObject(); 
        var args = {node: node, items: items};
        
        // Check for errors
        if (items.error)
        {
            this.onItemsError.call(this, args);
            return;
        }
        // All OK        
        this.onItemsLoaded.call(this, args);      
        this.processItems(node, items);
       
    }
    
    var onHttpError = function(req)
    {
        // Package up and call any error delegates
        self.removeLoadingIcon(node);
        var args = {node: node, http: req};
        this.onHttpError.call(this, args);
    }
    
    this.createLoadingIcon(node);
    
    
    
    var loader = new ajax.net.ContentLoader(this, onLoadHandler, onHttpError, this.url, 
        this.method, null, true);
        
    // Compile params from any that have been added to this class
    var params = [];    
    // Add any additional params
    for (var i = 0; i < this.requestParams.length; i++)
        params.push(this.requestParams[i]);
            
    // add the key (legacy) and node data if dataItem exists
    if (node.dataItem)
    {
        params.append("key=" + escape(node.dataItem.key));
        var data = ajax.lang.toJsonString(node.dataItem);
        params.append("data=" + escape(data));
    }
    // Send request with current params
    loader.requestParams = params;    
    loader.sendRequest();
}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.loadFromCallback = function(node)
{
    this.createLoadingIcon(node);
    // anon function is binded to this context...
    setTimeout(function(){this.processItems(node, this.callback(node))}.bind(this), this.callbackWait);
}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.createLoadingIcon = function(node)
{
    if (node.treeElements)
    {
        var loadingIcon = this.doc.createElement("img");
        loadingIcon.src = this.loadingIconSrc;
        this.copyProperties(loadingIcon.style, this.loadingIconStyle);
        node.treeElements.loadingIcon = loadingIcon;
        node.appendChild(loadingIcon);
    }
}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.removeLoadingIcon = function(node)
{
    if (node.treeElements && node.parentNode)
        node.removeChild(node.treeElements.loadingIcon);

}

//--------------------------------------------------------------
ajax.components.Treeview.prototype.getNodes = function(compareCallback)
{ // Pass in a function pointer that will return true if the node matches what you want
    var result = [];
    var seek = function(node)
    {
        if (node.dataItem && compareCallback(node))
            result.push(node);
            
        for (var i = 0; i < node.childNodes.length; i++)
            if (node.tagName && node.tagName == "DIV")
                seek(node.childNodes[i]);
    }
    seek(this.layer);
    return result;
}

//--------------------------------------------------------------
// Section for dealing with data updates from a DataTable.
ajax.components.Treeview.prototype.dataRowCompareCallback = function(row, node)
{   // Returns true if the node passed matches the row passed.
    // By default I have NO IDEA how to compare so return false
    // Clients requiring use of onDataTableUpdateHandler must point this callback to their own
    // implementation, or subclasses should override it.
    return false;
}
//--------------------------------------------------------------
ajax.components.Treeview.prototype.dataRowCreateItem = function(row)
{   // Returns a new tree item from the data row .
    // Again, by default I have NO IDEA how to create a tree node from the row passed.
    // Clients requiring the use of onDataTableUpdateHandler must point this method to their own
    // implementation (perhaps different for different row types represented in the tree), or subclasses should override it
    return new ajax.components.Treeview.Item();
}
ajax.components.Treeview.prototype.dataRowAttachesNode = function(row)
{   // Returns the node of the tree to attach any node created from this data row (row.parentID?)
    // Returning null will attach as a root node.
    // Clients requiring the use of onDataTableUpdateHandler must point this method to their own
    // implementation.
    return null;    
}
//--------------------------------------------------------------
ajax.components.Treeview.prototype.onDataTableUpdateHandler = function(newData, router)
{
    var dataTable = router.sender;
    
    // Set the compare callback and the create item converters to those set by the dataTable in question.
    this.dataRowCompareCallback = dataTable.treeViewDataRowCompareCallback;
    this.dataRowCreateItem      = dataTable.treeViewDataRowCreateItem;
    
    // Can we use the dataTable to tell us what we need to know in order to do the rest of this
    // in a generic fashion? 
    // Could a DataTable hold a treeCompare method (how to identify these records in a treeview)? 
    // Could/should this be a seperate object that can be used by the client AND the server code (to ensure the JSON is constructed correctly?)
    // Could it also have a method that would map it's own data to a tree item (to create new rows).
    // Finally - how could we know what node to attach a new item to?
    
    if (newData.length == 1) // We are only interested in single row returns
    {
        var row = newData[0];
                          
        // update the tree
                
        // Deal with Update, delete or Insert, use the Compare callback currently set to compare the node against the data row.
        var existingNodes = this.getNodes(function(node)
            {
                // pass the row AND the node to the data row compare callback.
                return this.dataRowCompareCallback(row, node);
            }.bind(this)
        );
                
        if (existingNodes.length > 0) // Update to existing Node
        {
            // Deal with "all" nodes (should only be one).
            for (var i = 0; i < existingNodes.length; i++)
            {
                var node = existingNodes[i];
                                    
                if (row[dataTable.deletedRowMarker])
                {
                    // Special - if this node is the selected node and it's just been deleted then reset the selectedNode
                    if (node == this.selectedNode)
                    {
                        // Attempt to select the parentnode
                        if (node.parentNode && node.parentNode != this.layer)
                            this.selectNode(node.parentNode);
                        else
                            this.unSelectNode();
                    }
                    this.removeNode(node);   // Deleted
                }
                else                        
                    node.setText(row.sectionName);      // updated                    
            }
        }
        else // Add New Node to active node
        {                
            // Use the currently set dataRowCreateItem method to create a new tree item from the data row
            var treeItem    = this.dataRowCreateItem(row);
            // The attaches node method must be specified by the controller of this tree.
            var attachToNode = this.dataRowAttachesNode(row) || this.layer;
            this.addNode(attachToNode, treeItem); // the node we add it to depends on the data we got.
        }
    } 
}



//--------------------------------------------------------------
ajax.components.Treeview.prototype.toString = function()
{
    return "ajax.components.Treeview";
}
//--------------------------------------------------------------
ajax.components.Treeview.prototype.toClassName = function()
{
    return ajax.lang.getClassName("ajax.components.Treeview");
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.cast = function(obj)
{///<returns type="ajax.components.Treeview"/>
    return obj;
}




//--------------------------------------------------------------
//Treeview.Item
ajax.components.Treeview.Item = function(type, img, words, key, imgSel, tag)
{
    this.type   = type || "";
    this.img    = img || "";
    this.words  = words || "";
    this.key    = key || "";
    this.imgSel = imgSel || "";
    this.tag    = tag || "";
    this.loaded = false;
}
ajax.components.Treeview.Item.prototype = 
{
    type: "",
    img: "",
    words: "",
    key: "",
    imgSel: "",
    tag: "",
    loaded: false
}
//-----------------------------------------------------------------------------
ajax.components.Treeview.Item.cast = function(obj)
{///<returns type="ajax.components.Treeview.Item"/>
    return obj;    
}

//-----------------------------------------------------------------------------
ajax.components.Treeview.TreeElements = function(icon, img, checkbox, txt, txtImg)
{
    this.icon       = icon;
    this.img        = img;
    this.checkbox   = checkbox;
    this.txt        = txt;
    this.txtImg     = txtImg;        
}
ajax.components.Treeview.TreeElements.prototype = 
{
    icon: null,
    img: null,
    checkbox: null,
    txt: null,
    txtImg: null
};
//-----------------------------------------------------------------------------
ajax.components.Treeview.TreeElements.cast = function(obj)
{///<returns type="ajax.components.Treeview.TreeElements"/>
    return obj;    
}
